﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Xml;
using System.Web.Services.Protocols;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.Configuration;
using System.Web;

using cz.uhul.DSMLv2;
using cz.uhul.DSMLv2.Services;
using cz.uhul.DSMLv2.Server.Sql;
using cz.uhul.Web.Support;

namespace cz.uhul.services.DSMLv2
{

  [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
  public class LdConnection : ILdConnection
  {


    protected LdServiceConfiguration conf;


    //public LdResponseDocument Process(LdRequestDocument dsmlRequest)
    public Message Process(Message reqMsg)
    {

      LdRequestDocument rd;

      //try parse inpub message as DSML batchRequest...
      try
      {
        rd = LdRequestDocument.Parse(reqMsg.GetReaderAtBodyContents());
      }
      catch (Exception e)
      {
        throw new SoapException(e.Message, SoapException.ClientFaultCode);
      }

      //resolve active configuration
      conf = ConfigurationManager.GetSection("LdServiceConfiguration") as LdServiceConfiguration;

      if (conf.SecurityModel == null)
      {
        //if no security model and batchRequest contains AuthZRequest, return error response
        if (rd.AuthRequest != null)
          return Message.CreateMessage(
            reqMsg.Version, ""
            , _fillResponseAsErr(
              rd, LdResultCodeEnum.InappropriateAuthentication
              , "not authorized", "error: authRequest not supported").ToXmlReader()
          );

        else
          //simply process batch with unlimited access permissions
          return Message.CreateMessage(reqMsg.Version, "", LdRequestServerExtension.ProcessBatch(rd).ToXmlReader());

      }
      else
      {
        //resolve security model...
        string batchOpPrincipalDn = conf.SecurityModel.LdOperationPrincipal;

        //resolve operation principal identity DN, replace token when need
        if (HttpContext.Current.Application["ConfigurationPropertyValueHandlers"] != null)
        {
          ConfigurationPropertyValueHandlers ch = (ConfigurationPropertyValueHandlers)HttpContext.Current.Application["ConfigurationPropertyValueHandlers"];
          batchOpPrincipalDn = ConfigurationPropertyValueHandlers.Format(batchOpPrincipalDn, ch, conf.RootDSE);
        }

        //validate operation principal identity Dn...
        string tokenDn = _getOpPrincipalTokenDn(batchOpPrincipalDn, conf.SecurityModel.LdSecurityTokenSuperPath, conf.SecurityModel.LdSecurityTokenOwnerAttribute);

        if (String.IsNullOrEmpty(tokenDn))
          return Message.CreateMessage(
            reqMsg.Version, ""
            , _fillResponseAsErr(
              rd, LdResultCodeEnum.InappropriateAuthentication
              , "not authorized", "error: permissions denied").ToXmlReader()
          );

        //resolve security model...
        if (conf.SecurityModel is LdAdminOnlySecurityModel)
        {
          //if admin only security model and batchRequest contains AuthZRequest, return error response
          if (rd.AuthRequest != null)
            return Message.CreateMessage(
              reqMsg.Version, ""
              , _fillResponseAsErr(
                rd, LdResultCodeEnum.InappropriateAuthentication
                , "not authorized", "error: authRequest not supported").ToXmlReader()
            );

          else
            return Message.CreateMessage(reqMsg.Version, "", LdRequestServerExtension.ProcessBatch(rd).ToXmlReader());

        }
        else
          //todo: resolve acl for each operation...
          throw new NotImplementedException();

      }

    }





    /// <summary>
    /// search for given Dn security token
    /// </summary>
    /// <param name="PrincipalDn"></param>
    /// <param name="securityAccessTokenPath"></param>
    /// <returns></returns>
    private static string _getOpPrincipalTokenDn(string PrincipalDn, string securityAccessTokenPath, string ownerAttribName)
    {
      LdSearchRequest sr = new LdSearchRequest()
      {
        DistinguishedName = securityAccessTokenPath
        ,DerefAliases = LdDerefAliasesEnum.neverDerefAliases
        ,Scope = LdSearchScopeEnum.wholeSubtree
        ,TypesOnly = false
        ,Filter = new FilterGroup()
        {
          EqualityMatch = new EqualityMatchOp()
          {
            Name = ownerAttribName
            ,Value = PrincipalDn
          }
        }
        ,Attributes = new LdAttrNameCollection()
        {
          new LdAttrName() { Name = ownerAttribName }
        }
      };

      LdSearchResponse srs = sr.Execute();

      if (srs.SearchResultEntries.Count == 0)
        return null;

      else
        return srs.SearchResultEntries[0].DistinguishedName;
    }





    /// <summary>
    /// when auth error, user cant process any request from batch.
    /// thus all requests in batch must return error
    /// </summary>
    /// <param name="rd"></param>
    /// <param name="response"></param>
    /// <param name="code"></param>
    /// <param name="description">description of problem</param>
    /// <param name="err">error message propagated from original error message</param>
    private static LdResponseDocument _fillResponseAsErr(LdRequestDocument rd, LdResultCodeEnum code, string description, string err)
    {

      LdResponseDocument response = new LdResponseDocument();

      //handle add req
      if (rd.ModifyRequests != null)
        foreach (LdModifyRequest r in rd.ModifyRequests)
          response.ModifyResponses.Add(new LdModifyResponse(code, description, err));

      if (rd.SearchRequests != null)
        foreach (LdSearchRequest r in rd.SearchRequests)
          response.SearchResponses.Add(new LdSearchResponse(code, description, err));

      if (rd.AddRequests != null)
        foreach (LdAddRequest r in rd.AddRequests)
          response.AddResponses.Add(new LdAddResponse(code, description, err));

      if (rd.DeleteRequests != null)
        foreach (LdDeleteRequest r in rd.DeleteRequests)
          response.DeleteResponses.Add(new LdDeleteResponse(code, description, err));

      if (rd.ModifyDnRequests != null)
        foreach (LdModifyDnRequest r in rd.ModifyDnRequests)
          response.ModifyDnResponses.Add(new LdModifyDnResponse(code, description, err));

      if (rd.CompareRequests != null)
        foreach (LdCompareRequest r in rd.CompareRequests)
          response.CompareResponses.Add(new LdCompareResponse(code, description, err));

      if (rd.ExtendedRequests != null)
        foreach (LdExtendedRequest r in rd.ExtendedRequests)
          response.ExtendedResponses.Add(new ExtendedResponse(code, description, err));

      return response;
    }




  }

}
